package com.cyao.core;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import net.greghaines.jesque.client.Client;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.util.HashMap;

/**
 * 定时任务
 *
 * @author Cyao
 */
public class ScheduleJob implements Runnable {
    public static final String QUEUE_NAME = "default";
    private Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * spring bean名称
     */
    private String beanName;

    /**
     * 方法名
     */
    private String methodName;

    /**
     * cron表达式
     */
    private String cronExpression;

    /**
     * 参数
     */
    private String params;

    public ScheduleJob() {

    }

    public ScheduleJob(String beanName, String methodName, String cronExpression) {
        this.beanName = beanName;
        this.methodName = methodName;
        this.cronExpression = cronExpression;
    }

    public ScheduleJob(String beanName, String methodName, String cronExpression, String params) {
        this(beanName, methodName, cronExpression);
        this.params = params;
    }

    public String getBeanName() {
        return beanName;
    }

    public void setBeanName(String beanName) {
        this.beanName = beanName;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public String getCronExpression() {
        return cronExpression;
    }

    public void setCronExpression(String cronExpression) {
        this.cronExpression = cronExpression;
    }

    public String getParams() {
        return params;
    }

    public void setParams(String params) {
        this.params = params;
    }

    private String jobName() {
        return getBeanName() + "." + getMethodName();
    }

    @Override
    public void run() {
        try {
            // 任务开始时间
            long startTime = System.currentTimeMillis();

            // 执行任务
            logger.info("任务准备执行，任务ID：" + jobName());

            Object target = SpringContextUtils.getBean(getBeanName());

            Method method;
            if (StringUtils.isEmpty(getParams())) {
                method = target.getClass().getDeclaredMethod(getMethodName());
            } else {
                method = target.getClass().getDeclaredMethod(getMethodName(), ScheduleParams.class);
            }

            ReflectionUtils.makeAccessible(method);

            if (StringUtils.isEmpty(getParams())) {
                method.invoke(target);
            } else {
                method.invoke(target, ScheduleParams.readValue(getParams()));
            }

            // 任务执行总时长
            long times = System.currentTimeMillis() - startTime;

            logger.info("任务执行完毕，任务ID：" + jobName() + "  总共耗时：" + times + "毫秒");

        } catch (Exception e) {
            logger.error("任务执行失败，任务ID：" + jobName(), e);
        } finally {
            if (!StringUtils.isEmpty(getCronExpression())) {
                Client client = SpringContextUtils.getBean("client", Client.class);
                if (StringUtils.isEmpty(getParams())) {
                    ScheduleUtils.cronEnqueue(client, getBeanName(), getMethodName(), getCronExpression());
                } else {
                    ScheduleParams scheduleParams = ScheduleParams.readValue(getParams());
                    Object[] args = new Object[scheduleParams.size()];
                    for (int i = 0; i < scheduleParams.size(); i++) {
                        args[i] = scheduleParams.get("arg" + i);
                    }
                    ScheduleUtils.cronEnqueue(client, getBeanName(), getMethodName(), getCronExpression(), args);
                }
            }
        }
    }

    public static class ScheduleParams extends HashMap<String, Object> {
        private static final ObjectMapper objectMapper = new ObjectMapper();
        private static final long serialVersionUID = 1L;

        static {
            objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        }

        public ScheduleParams put(String key, Object value) {
            super.put(key, value);
            return this;
        }

        public String toJsonString() {
            try {
                return objectMapper.writeValueAsString(this);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        public static ScheduleParams readValue(String content) {
            try {
                return objectMapper.readValue(content, ScheduleParams.class);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
}
